package main

import (
	"archive/zip"
	"bytes"
	"encoding/base64"
	"fmt"
	"net/http"
	"os"

	"gitee.com/zh-jn/harmonica-previewer-online/api"
	"gitee.com/zh-jn/harmonica-previewer-online/gateway"
	"github.com/gin-gonic/gin"
)

type config struct {
	gateway.PreviewerCgiConfig
	previewerPoolNamespace string
	previewerPort          int

	cgiListenAddr string
	cgiListenPort int

	gatewayListenAddr string
	gatewayListenPort int
	wsTimeout         int
}

func CollectConfigFromEnv() config {
	return config{
		PreviewerCgiConfig: gateway.PreviewerCgiConfig{
			ManagerAddr:  api.ParseString(os.Getenv("Gateway_ManagerAddr"), "127.0.0.1"),
			ManagerPort:  api.ParseIntValue(os.Getenv("Gateway_ManagerPort"), 8082),
			CompilerAddr: api.ParseString(os.Getenv("Gateway_CompilerAddr"), "127.0.0.1"),
			CompilerPort: api.ParseIntValue(os.Getenv("Gateway_CompilerPort"), 8081),
			StorageAddr:  api.ParseString(os.Getenv("Gateway_StorageAddr"), "127.0.0.1"),
			StoragePort:  api.ParseIntValue(os.Getenv("Gateway_StoragePort"), 8080),
			SidecarPort:  api.ParseIntValue(os.Getenv("Gateway_SidecarPort"), 8084),
			SessionKey:   api.ParseString(os.Getenv("Gateway_SessionKey"), "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"),
		},
		previewerPoolNamespace: api.ParseString(os.Getenv("Gateway_PreviewerPoolNamespace"), "previewer-pool"),
		previewerPort:          api.ParseIntValue(os.Getenv("Gateway_PreviewerPort"), 80),

		cgiListenAddr:     api.ParseString(os.Getenv("Gateway_CgiListenAddr"), "0.0.0.0"),
		cgiListenPort:     api.ParseIntValue(os.Getenv("Gateway_CgiListenPort"), 59462),
		gatewayListenAddr: api.ParseString(os.Getenv("Gateway_GatewayListenAddr"), "0.0.0.0"),
		gatewayListenPort: api.ParseIntValue(os.Getenv("Gateway_GatewayListenPort"), 59463),
		wsTimeout:         api.ParseIntValue(os.Getenv("Gateway_WsTimeout"), 60),
	}
}

var cgi *gateway.PreviewerCgi
var gw *gateway.PreviewerGateway

func main() {
	cfg := CollectConfigFromEnv()

	cgi = gateway.NewPreviewerCgi(cfg.PreviewerCgiConfig)
	gw = gateway.NewPreviewerGateway(cfg.previewerPoolNamespace, cfg.previewerPort, cfg.ManagerAddr, cfg.ManagerPort, cfg.SessionKey, cfg.wsTimeout)

	// start gateway
	go func() {
		if err := http.ListenAndServe(fmt.Sprintf("%v:%v", cfg.gatewayListenAddr, cfg.gatewayListenPort), gw); err != nil {
			panic(err)
		}
	}()

	cgiRouter := gin.New()
	cgiRouter.Use(gin.Recovery())
	api := cgiRouter.Group("/api")
	// api.OPTIONS("/*all", func(c *gin.Context) {
	// c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
	// c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
	// return
	// })
	api.POST("/compile-code", CompileCode)
	api.POST("/compile-code-b64", CompileCodeB64)
	api.POST("/launch-previewer", LaunchPreviewer)
	api.POST("/release-previewer", ReleasePreviewer)
	api.GET("/session-id", GetSessionId)

	if err := cgiRouter.Run(fmt.Sprintf("%v:%v", cfg.cgiListenAddr, cfg.cgiListenPort)); err != nil {
		panic(err)
	}
}

const HeaderKey_SessionId = "session-id"

func CompileCodeB64(c *gin.Context) {
	sessionId := c.Request.Header.Get(HeaderKey_SessionId)
	var req = struct {
		Js  string `json:"js"`
		Hml string `json:"hml"`
		Css string `json:"css"`
	}{}
	err := c.BindJSON(&req)
	if err != nil {
		c.JSON(http.StatusOK, api.Rsp(nil, err))
		return
	}
	jsbuf, err := base64.StdEncoding.DecodeString(req.Js)
	if err != nil {
		c.JSON(http.StatusOK, api.Rsp(nil, fmt.Errorf("decode js err, %v", err)))
		return
	}
	hmlbuf, err := base64.StdEncoding.DecodeString(req.Hml)
	if err != nil {
		c.JSON(http.StatusOK, api.Rsp(nil, fmt.Errorf("decode hml err, %v", err)))
		return
	}
	cssbuf, err := base64.StdEncoding.DecodeString(req.Css)
	if err != nil {
		c.JSON(http.StatusOK, api.Rsp(nil, fmt.Errorf("decode css err, %v", err)))
		return
	}

	zipbuf := bytes.NewBuffer(nil)
	zipWriter := zip.NewWriter(zipbuf)

	jswriter, err := zipWriter.Create("index.js")
	if err != nil {
		c.JSON(http.StatusOK, api.Rsp(nil, fmt.Errorf("create zip jswriter err, %v", err)))
		return
	}
	if _, err = jswriter.Write(jsbuf); err != nil {
		c.JSON(http.StatusOK, api.Rsp(nil, fmt.Errorf("write js to zip err, %v", err)))
		return
	}

	hmlwriter, err := zipWriter.Create("index.hml")
	if err != nil {
		c.JSON(http.StatusOK, api.Rsp(nil, fmt.Errorf("create zip hmlwriter err, %v", err)))
		return
	}
	if _, err = hmlwriter.Write(hmlbuf); err != nil {
		c.JSON(http.StatusOK, api.Rsp(nil, fmt.Errorf("write hml to zip err, %v", err)))
		return
	}

	csswriter, err := zipWriter.Create("index.css")
	if err != nil {
		c.JSON(http.StatusOK, api.Rsp(nil, fmt.Errorf("create zip csswriter err, %v", err)))
		return
	}
	if _, err = csswriter.Write(cssbuf); err != nil {
		c.JSON(http.StatusOK, api.Rsp(nil, fmt.Errorf("write css to zip err, %v", err)))
		return
	}

	if err = zipWriter.Close(); err != nil {
		c.JSON(http.StatusOK, api.Rsp(nil, fmt.Errorf("close zip writer err, %v", err)))
		return
	}

	jsBundleId, err := cgi.CompileCode(sessionId, zipbuf)
	if err != nil {
		err = fmt.Errorf("compile jsbundle err, %v", err)
		c.JSON(http.StatusOK, api.Rsp(
			nil,
			err,
		))
		return
	}
	defer c.Request.Body.Close()
	c.JSON(http.StatusOK, api.Rsp(
		gin.H{
			"jsBundleId": jsBundleId,
		},
		nil,
	))
	return
}

func CompileCode(c *gin.Context) {
	sessionId := c.Request.Header.Get(HeaderKey_SessionId)
	jsBundleId, err := cgi.CompileCode(sessionId, c.Request.Body)
	if err != nil {
		err = fmt.Errorf("compile jsbundle err, %v", err)
		c.JSON(http.StatusOK, api.Rsp(
			nil,
			err,
		))
		return
	}
	defer c.Request.Body.Close()
	c.JSON(http.StatusOK, api.Rsp(
		gin.H{
			"jsBundleId": jsBundleId,
		},
		nil,
	))
	return
}

func ReleasePreviewer(c *gin.Context) {
	sessionId := c.Request.Header.Get(HeaderKey_SessionId)
	// jsBundleId, ok := c.GetQuery("jsBundleId")
	// if !ok {
	// 	c.JSON(http.StatusOK, api.Rsp(
	// 		nil,
	// 		fmt.Errorf("missing param [jsBundleId]"),
	// 	))
	// 	return
	// }
	previewerId, ok := c.GetQuery("previewerId")
	if !ok {
		c.JSON(http.StatusOK, api.Rsp(
			nil,
			fmt.Errorf("missing param [previewerId]"),
		))
		return
	}
	if err := cgi.ReleasePreviewer(sessionId, previewerId); err != nil {
		c.JSON(http.StatusOK, api.Rsp(
			nil,
			err,
		))
		return
	}
	c.JSON(http.StatusOK, api.Rsp(nil, nil))
	return
}

func LaunchPreviewer(c *gin.Context) {
	sessionId := c.Request.Header.Get(HeaderKey_SessionId)
	jsBundleId, ok := c.GetQuery("jsBundleId")
	if !ok {
		c.JSON(http.StatusOK, api.Rsp(
			nil,
			fmt.Errorf("missing param [jsBundleId]"),
		))
		return
	}
	previewerD, err := cgi.LaunchPreviewer(sessionId, jsBundleId)
	if err != nil {
		c.JSON(http.StatusOK, api.Rsp(
			nil,
			err,
		))
		return
	}
	c.JSON(http.StatusOK, api.Rsp(
		gin.H{
			"previewerKey": fmt.Sprintf("%v-%v", previewerD.PreviewerId, sessionId),
		},
		nil,
	))
	return
}

func GetSessionId(c *gin.Context) {
	sessionId, err := cgi.GetSessionId()
	if err != nil {
		c.JSON(http.StatusOK, api.Rsp(nil, err))
		return
	}
	c.JSON(http.StatusOK, api.Rsp(
		gin.H{
			"session-id": sessionId,
		},
		nil,
	))
	return
}
